import { dom, EventHandler, Nodes } from '@knno/dom';
import { Action } from '../action/action';
import { DATA } from '../symbol';
import { pickerStyle } from './picker.css';
import { button } from '../button/button';
import { Tab, tab } from '../tab/tab';
import { Roller, roller } from '../roller/roller';
import { Dialog } from '../dialog/dialog';
import { isMobile } from '../adaptivePopup/adaptivePopup';
import '../../dict';
import { DICT } from '../../dict';

export type PickValue = string | number;

export interface PickerEventMap {
	groupactive: CustomEvent<number>;
}
export type PickerEventKey = keyof PickerEventMap;
export type PickerEventHandler<T extends PickerEventKey, O extends Nodes> = (this: O, event: PickerEventMap[T]) => void;

export interface Picker {
	on<K extends keyof PickerEventMap>(
		event: K,
		listener: PickerEventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	on<K extends keyof HTMLElementEventMap>(
		event: K,
		listener: EventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	off<K extends keyof PickerEventMap>(
		event: K,
		listener: PickerEventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
	off<K extends keyof HTMLElementEventMap>(
		event: K,
		listener: EventHandler<K, this>,
		options?: boolean | AddEventListenerOptions
	): this;
}

export type PickUpdateOption = { keepPosition: boolean; gid: number; pid: number };
export type PickValueFunc = (selection: PickValue[][], option: PickUpdateOption) => PickValue[];
export type PickOptions = PickValue[] | PickValueFunc;

export interface PickGroup {
	name: string;
	parts: PickOptions[];
}

export type ShowPickOptions = {
	title?: string;
	optionGroups?: PickGroup[];
	options?: PickOptions[];
	display?: 'action' | 'dialog' | 'auto';
	selection?: PickValue[][];
};

export class Picker extends Nodes {
	private [DATA]: {
		groups: (PickGroup & { rollers: Roller[] })[];
		selection: PickValue[][];
		tab: Tab;
	};

	constructor() {
		const t = tab().autoHideTabs(true);
		t.on('tabactive', (e) => {
			this.render();
			this.emit(new CustomEvent('groupactive', { detail: e.detail.page }));
		});
		const box = dom.div(pickerStyle).append(t);
		super(box);
		this[DATA] = { groups: [], selection: [], tab: t };
	}

	private render() {
		const tb = this[DATA].tab;
		tb.tabs(this[DATA].groups.map((g) => g.name));
		tb.html('').append(...this[DATA].groups.map((g, idx) => this.renderGroup(g, idx)));
	}

	private renderGroup(group: PickGroup, gid: number): Nodes {
		return dom.div('group').append(
			...group.parts.map((part, pid) => {
				let options: PickValue[];
				if (typeof part === 'function') {
					options = part(this.value(), { keepPosition: false, gid, pid });
				} else {
					options = part;
				}
				const r = roller()
					.options(options)
					.on('change', () => {
						// console.log('before roller changed:', this.value());
						if (this[DATA].selection[gid] === undefined) {
							this[DATA].selection[gid] = [];
						}
						this[DATA].selection[gid][pid] = r.value() ?? options[0];
						if (pid + 1 < group.parts.length) {
							this.updateGroup(gid, pid + 1);
						}
						// console.log('after roller changed:', this.value());
						this.emit(new Event('change'));
					});
				this[DATA].groups[gid].rollers[pid] = r;
				let selectGroup = this[DATA].selection[gid];
				if (!selectGroup) {
					selectGroup = [];
					this[DATA].selection[gid] = selectGroup;
				}
				let v = selectGroup[pid];
				if (v) {
					r.value(v);
				}
				v = r.value() ?? options[0];
				selectGroup[pid] = v;
				return r;
			})
		);
	}

	private updateGroup(gid: number, pid: number) {
		const group = this[DATA].groups[gid];
		for (let i = pid; i < group.parts.length; i++) {
			const part = group.parts[i];
			let options: PickValue[];
			const updateOption = { keepPosition: false, gid, pid };
			if (typeof part === 'function') {
				options = part(this.value(), updateOption);
			} else {
				options = part;
			}
			const r = group.rollers[i];
			let pos = r.selected();
			r.options(options);
			if (updateOption.keepPosition) {
				if (pos >= options.length) {
					pos = options.length - 1;
				}
				r.selected(pos);
			}
			this[DATA].selection[gid][i] = r.value() ?? options[0];
		}
	}

	activeGroup(): number;
	activeGroup(index: number): this;
	activeGroup(index?: number): number | this {
		if (index === undefined) {
			return this[DATA].tab.active();
		}
		this[DATA].tab.active(index);
		return this;
	}

	optionGroups(): PickGroup[];
	optionGroups(groups: PickGroup[]): this;
	optionGroups(groups?: PickGroup[]): this | PickGroup[] {
		if (groups === undefined) {
			return this[DATA].groups.map((g) => ({ name: g.name, parts: g.parts }));
		}
		this[DATA].groups = groups.map((g) => ({ ...g, rollers: [] }));
		this[DATA].selection = groups.map(() => []);
		this.render();
		return this;
	}

	options(options: PickOptions[]): this {
		this[DATA].groups = [{ name: '', parts: options, rollers: [] }];
		this[DATA].selection = [[]];
		this.render();
		return this;
	}

	value(): PickValue[][];
	value(selection: PickValue[][]): this;
	value(selection?: PickValue[][]): this | PickValue[][] {
		if (selection === undefined) {
			return this[DATA].selection;
		}
		this[DATA].selection = selection;
		this.render();
		return this;
	}

	static show(options: ShowPickOptions): Promise<PickValue[][] | null> {
		return new Promise<PickValue[][] | null>(function (resolve) {
			const pk = picker().css('border', 'none');
			if (options.optionGroups) {
				pk.optionGroups(options.optionGroups);
			} else if (options.options) {
				pk.options(options.options);
			}
			if (options.selection) {
				pk.value(options.selection);
			}
			function showAction() {
				const a = Action.show(pk)
					.addClass('no-padding')
					.title(options.title ?? '')
					.onOK(() => {
						a.close();
						resolve(pk.value());
					});
				a.on('close', () => {
					resolve(null);
				});
			}
			function showDialog() {
				const dlg = Dialog.create(pk).show().autoClose(true);
				if (options.title) {
					dlg.title(options.title);
				}
				dlg
					.buttons(
						button()
							.text(DICT.cancel)
							.on('click', () => {
								dlg.close();
							}),
						button()
							.text(DICT.ok)
							.type('primary')
							.on('click', (evt) => {
								console.log(evt.target);
								dlg.close();
								resolve(pk.value());
							})
					)
					.on('close', () => {
						resolve(null);
					});
			}
			if (options.display === 'dialog') {
				showDialog();
			} else if (options.display === 'action') {
				showAction();
			} else {
				if (isMobile()) {
					showAction();
				} else {
					showDialog();
				}
			}
		});
	}
}

export function picker(...className: string[]): Picker {
	return new Picker().addClass(...className);
}
